package com.sunxboy.platform.log4j;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.xml.DOMConfigurator;
import org.springframework.core.io.InputStreamSource;

import freemarker.template.Configuration;
import freemarker.template.Template;

public class Log4jConfigurator {
    private Level defaultLogLevel = Level.INFO;
    private static final AtomicBoolean defaultLoggingInitialized = new AtomicBoolean(false);
    private Map<String, Object> extraData;
    private static final String pattern = "%-5p %d{ISO8601} [%t]:%c %x - %m%n";

    public void setData(Map<String, Object> data) {
        this.extraData = new HashMap<String, Object>(data);
        this.extraData.put("pattern", pattern);
    }

    public void initializeBootstrapLogging() {
        initializeBootstrapLogging(false);
    }

    public void initializeBootstrapLogging(boolean force) {

        if (defaultLoggingInitialized.compareAndSet(false, true) || force) {
            Logger rootLogger = LogManager.getRootLogger();
            rootLogger.setAdditivity(false);
            rootLogger.removeAllAppenders();
            rootLogger.addAppender(new ConsoleAppender(getLayout()));
            rootLogger.setLevel(getDefaultLogLevel());
        }
        else {
            System.err.println("Bootstrap logging setup skipped, as it was already done");
            (new RuntimeException()).printStackTrace(System.err);
        }
    }

    private Layout getLayout() {
        return new PatternLayout(pattern);
    }

    public Level getDefaultLogLevel() {
        return defaultLogLevel;
    }

    public void setDefaultLogLevel(Level defaultLogLevel) {
        this.defaultLogLevel = defaultLogLevel;
    }

    public void configureFromTemplate(InputStreamSource log4jXmlConfigInputStreamSource) {
        boolean configChanged = false;
        try {
            Configuration configuration = new Configuration();
            InputStreamReader streamReader = new InputStreamReader(log4jXmlConfigInputStreamSource.getInputStream());
            Template template = new Template("", streamReader, configuration);

            final byte[] processedLogConfigData;
            {
                Map<String, Object> data = new HashMap<String, Object>();

                data.put("env", System.getenv());
                data.put("sys", System.getProperties());

                /*
                 * Make extra data available to the log configuration template, if any
                 */
                if (this.extraData != null) {
                    data.putAll(this.extraData);
                }

                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                PrintWriter writer = new PrintWriter(outputStream);
                template.process(data, writer);
                writer.close();
                processedLogConfigData = outputStream.toByteArray();
            }

            DOMConfigurator configurator = new DOMConfigurator();
            InputStream processedLogInputStream = new ByteArrayInputStream(processedLogConfigData);

            configChanged = true;
            configurator.doConfigure(processedLogInputStream, LogManager.getLoggerRepository());
        }
        catch (Exception e) {

            /**
             * If we failed while reconfiguring fail back to bootstrap logging
             */
            if (configChanged) {
                initializeBootstrapLogging(true);
            }
            throw new RuntimeException("Unable to configure log4j", e);
        }
    }

}
